![]() |
![]() |
|
22.5.3 Bilder mit der Klasse »Image«
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static Image FromFile(string); |
Wir wollen diese Methode im folgenden Beispielprogramm benutzen und eine JPEG-Datei in einer Form anzeigen. Sie finden das Bild auf der Buch-CD unter
...\Bilder\Egypt.jpg
Kommen wir zum Programmcode.
| // -------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 22\ImageAnzeigen |
| // -------------------------------------------------------------- |
| private void Form1_Paint(object sender, PaintEventArgs e) { |
| try { |
| string strFile = "..\\..\\..\\..\\Bilder\\Egypt.jpg"; |
| Image img = Image.FromFile(strFile); |
| e.Graphics.DrawImage(img, 0 ,0); |
| } |
| catch { |
| // Anweisungen |
| } |
| } |
Der Programmcode ist im Ereignishandler des Paint-Ereignisses implementiert, denn um das Bild im Arbeitsbereich der Form auszugeben, greifen wir auf eine Methode des Graphics-Objekts zu, die wir bisher noch nicht behandelt haben: DrawImage gibt ein Image-Objekt an einer bestimmten Position aus. Hierbei handelt es sich um den linken oberen Koordinatenpunkt des Bereichs, in den gezeichnet werden soll. Im Beispiel ist das der Ursprung (0, 0). Die Referenz auf das Image besorgen wir uns vorher mit der statischen FromFile-Methode der Klasse Image, der wir die Zeichenfolge, die den Pfad auf die Bilddatei beschreibt, übergeben. Wird die Datei nicht gefunden, wird eine Ausnahme ausgelöst, die behandelt werden muss.
| Hinweis In diesem Beispiel ist der Zugriff auf das Bild aus Demonstrationszwecken hardcodiert. In der Praxis sollten Sie das jedoch grundsätzlich vermeiden. |
Haben Sie sich die Definition der Methode DrawImage in der .NET-Dokumentation angesehen? Wenn nicht, sollten Sie das nachholen, denn die insgesamt 30 Überladungen bieten eine Vielzahl von Möglichkeiten zur Bildausgabe: Bilder können gestreckt, gestaucht oder unter einem Drehwinkel angezeigt werden.
Zwei weitere Beispiele sollen die Flexibilität der Zeichenmethode DrawImage demonstrieren. Sie brauchen dazu nur die Zeile des Methodenaufrufs von DrawImage im Beispiel oben gegen die folgende Anweisung auszutauschen:
| e.Graphics.DrawImage(img, new Rectangle(0, 0, 250, 150)); |
Dem Methodenaufruf liegt die überladene Version zugrunde, die ein Rectangle-Objekt erwartet, das die Lage und die Abmessungen des Bereichs beschreibt, in dem das Bild angezeigt werden soll. Die daraus resultierende Bildausgabe sehen Sie in der folgenden Abbildung – das Bild wird in seiner Breite gestreckt.

Hier klicken, um das Bild zu vergrößern
Abbildung 22.29 Ausgabe eines gestreckten Bildes
Tauschen wir noch einmal den DrawImage-Aufruf gegen einen anderen aus und übergeben ein Point-Array. Dieser Übergabe liegt die folgende überladene Methode zugrunde:
| public void DrawImage(Image image, Point[] destPoints); |
In diesem Point-Array sind drei Punkte definiert: Der erste gibt den Lagepunkt der oberen linken Ecke des Bildes an, der zweite den Lagepunkt der oberen rechten und der dritte den der unteren rechten Ecke. Der vierte Punkt wird automatisch so ermittelt, dass das Ergebnis ein Parallelogramm bildet:
| e.Graphics.DrawImage(img,new Point[]{new Point(80,0), |
| new Point(300,80), |
| new Point(30,100)}); |

Hier klicken, um das Bild zu vergrößern
Abbildung 22.30 Image, gedreht und gestreckt angezeigt
Das Zeichnen auf einem Image ist nicht weiter schwierig. Wir rufen dazu sowohl die Methode DrawImage als auch die Methode DrawString auf, der wir nur die Zeichenfolge samt der Schriftart und -farbe und natürlich auch die Koordinaten, an denen die Schrift ausgegeben werden soll, übergeben.
| // -------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 22\AufImageZeichnen |
| // -------------------------------------------------------------- |
| private void Form1_Paint(object sender, PaintEventArgs e) { |
| string strFile = "..\\..\\..\\..\\Bilder\\Egypt.jpg"; |
| Image img = Image.FromFile(strFile); |
| string strText = "Ägyptischer Restaurateur"; |
| Font font = new Font("Arial", 12, FontStyle.Bold | FontStyle.Underline); |
| e.Graphics.DrawImage(img, 0, 0); |
| e.Graphics.DrawString(strText, font, Brushes.Yellow, new Point(5,5)); |
| } |

Hier klicken, um das Bild zu vergrößern
Abbildung 22.31 Bildausgabe mit Beschriftung
Als abstrakte Basisklasse stellt Image Eigenschaften bereit, die von den beiden abgeleiteten Klassen Metafile und Bitmap geerbt werden. Diese Eigenschaften stellen uns Informationen über das Image-Objekt zur Verfügung. In der folgenden Tabelle sind einige wichtige Eigenschaften wiedergegeben.
| Eigenschaft | Beschreibung |
| Height | Liefert die Höhe des Bildes in Pixel. |
| HorizontalResolution | Liefert die horizontale Auflösung des Bildes in DPI (Dots per Inch). |
| PhysicalDimension | Liefert ein SizeF-Objekt zurück, das die Größe des Bildes beschreibt. Die Dokumentation gibt keine Auskunft über die Einheiten, vermutlich handelt es sich jedoch um 1/100 mm. |
| PixelFormat | Ruft das Pixelformat des Bildes ab. |
| RawFormat | Ruft das Bildformat des Images ab. Der Rückgabewert ist vom Typ ImageFormat (siehe Tabelle 22.11). |
| VerticalResolution | Liefert die vertikale Auflösung des Bildes in DPI (Dots per Inch). |
| Width | Die Breite des Bildes in Pixel. |
Die Klasse Image ist, wie wir gesehen haben, nicht instanziierbar. Ganz anders die Klasse Bitmap, die aus Image abgeleitet ist. Dem überladenen Konstruktor können Sie dabei die Referenz auf ein Image-Objekt, einen Stream oder eine Zeichenfolge übergeben, die den Pfad zu einer Bilddatei beschreibt. Am einfachsten lässt sich eine Bitmap aber mit dem Konstruktor erzeugen, der zwei Integer erwartet, mit denen die Breite und die Höhe der Bitmap in Pixel beschrieben werden:
Bitmap bmp = new Bitmap(300, 400);
Die erzeugte Bitmap ist 300 Pixel breit und 400 Pixel hoch. Es handelt sich um eine ARGB-Bitmap (A = Alpha-Kanal, R = Rot, G = Grün, B = Blau), deren Werte mit 0 initialisiert werden. Da der A-Kanal zur Darstellung der Transparenz den Startwert 0 hat, bedeutet das, dass eine Bitmap (zunächst) durchsichtig ist.
Mit der Methode SetPixel können Sie den Zustand eines einzelnen Pixels in der Bitmap ändern:
| bmp.SetPixel(10, 10, Color.Blue); |
Die ersten beiden Parameter geben die Koordinaten eines Pixels an, der mit der im dritten Parameter genannten Farbe gezeichnet wird. Mit der DrawImage-Methode des Graphics-Objekts können Sie sich die Bitmap auf einer grafikfähigen Oberfläche anzeigen lassen. Dabei darf es sich sogar um eine Schaltfläche handeln, die auch eine Paint-Methode bereitstellt. Der Beispielcode unten benutzt die wohl einfachste Form der DrawImage-Zeichenmethode, aber es kann natürlich auch jede beliebige Überladung aufgerufen werden, um sehr ansehnliche optische Effekte zu erzielen.
| private void button1_Paint(object sender, PaintEventArgs e) { |
| Bitmap bmp = new Bitmap(255, 255); |
| for (int i = 0; i < 255; i++) |
| for (int j = 0; j < 255; j++) |
| bmp.SetPixel(j, i, Color.FromArgb(i, 0, j)); |
| e.Graphics.DrawImage(bmp, 0, 0); |
| } |
Die Bitmap, die im Codefragment erzeugt wird, hat eine Höhe und Breite von jeweils 255 Pixeln. In zwei for-Schleifen wird für jedes Pixel in der Bitmap eine neue Farbe festgelegt. Die äußere Schleife durchläuft dabei jede Pixelzeile, die innere jede Pixelspalte. Die Farbe des Rot- und Blauanteils wird aus dem jeweiligen Zählerstand mit der statischen FromArgb-Methode der Color-Klasse ermittelt.
Benötigen Sie die Farbe eines Pixels, hilft das Pendant der SetPixel-Methode weiter: GetPixel. Sie müssen wieder die Koordinaten des entsprechenden Pixels angeben und erhalten als Rückgabewert die Color-Struktur, die der Farbe entspricht:
Wie ein Bild aus einer Datei geladen werden kann, haben Sie in den vergangenen Abschnitten gesehen. Ebenso einfach ist es, ein Image in einer Datei zu speichern. Die Klasse Image bietet dazu die überladene Methode Save an, die von der abgeleiteten Klasse Bitmap geerbt wird. Von dieser Methode gibt es eine einparametrige Version, die nur die Angabe des Speicherpfades als Zeichenfolge entgegennimmt. Diese einzusetzen empfiehlt sich allerdings nicht, da das Bildformat keinem bekannten Bildformat entspricht und sich die Datei sich aus dem Windows Explorer heraus nicht öffnen lässt.
Sie sollten beim Speichern grundsätzlich immer das Bildformat angeben, und dazu ist die folgende Überladung der Save-Methode geeignet:
| public void Save(string, ImageFormat); |
Anstelle des String-Parameters können Sie die Bitmap auch an ein Stream-Objekt übergeben.
Der Parameter vom Typ ImageFormat basiert auf der nicht ableitbaren Klasse System.Drawing.Imaging.ImageFormat, die über insgesamt elf statische Eigenschaften das Bildformat abruft. Von diesen elf Eigenschaften sind für uns nur zehn von Interesse, die der nachfolgenden Tabelle entnommen werden können.
| Eigenschaft | Beschreibung |
| Bmp | Ruft das Bitmap-Bildformat (BMP) ab. |
| Emf | Ruft das Windows-Bildformat Erweiterte Metadatei (Enhanced Meta File – EMF) ab. |
| Exif | Ruft das Exif-Format (Exchangeable Image File) ab. |
| Gif | Ruft das GIF-Bildformat (Graphics Interchange Format) ab. |
| Icon | Ruft das Bildformat für Windows-Symbole ab. |
| Jpeg | Ruft das JPEG-Format (Joint Photographic Experts Group) ab. |
| MemoryBmp | Ruft ein Bitmap-Bildformat im Speicher ab. |
| Png | Ruft das PNG-Bildformat (W3C Portable Network Graphics) ab. |
| Tiff | Ruft das TIFF-Bildformat (Tagged Image File Format) ab. |
| Wmf | Ruft das WMF-Bildformat (Windows Metafile) ab. |
Zum Speichern geben wir jetzt nur den Pfad und den Dateinamen an. Beachten Sie dabei, dass die Dateierweiterung mit der Angabe des Bildformats übereinstimmt:
| bmp.Save(@"C:\MyBitmap.jpg", ImageFormat.Jpeg); |
Je nachdem, welches Format Sie angeben, wird das zu speichernde Bild mit dem entsprechenden Verfahren komprimiert. Speichern Sie die Bitmap beispielsweise als GIF-Datei, verringert sich die Anzahl der Farben auf 256, was zu einem Verlust der Darstellungsqualität führt.
Im folgenden Beispielprogramm kann mit der Maus eine Grafik auf die Clientfläche der Form gezeichnet werden. Dazu wird die linke Maustaste gedrückt, und mit gedrückter Maustaste wird die »Zeichnung« erstellt.
| // -------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 22\Einfaches Malprogramm |
| // -------------------------------------------------------------- |
| public partial class Form1 : Form { |
| private bool mouseFlag; |
| private Point lastPoint; |
| ... |
| private void Form1_MouseMove(object sender, MouseEventArgs e) { |
| // prüfen, ob die linke Maustaste gedrückt ist |
| if (!mouseFlag) |
| return; |
| // aus den Mauskoordinaten ein Point-Objekt erzeugen |
| Point newPoint = new Point(e.X, e.Y); |
| // Referenz auf den Grafikkontext der Form besorgen |
| Graphics grfx = this.CreateGraphics(); |
| // Linie zeichnen |
| grfx.DrawLine(new Pen(Brushes.Yellow), lastPoint, newPoint); |
| lastPoint = newPoint; |
| grfx.Dispose(); |
| } |
| private void Form1_MouseDown(object sender, MouseEventArgs e) { |
| // prüfen, ob die linke Maustaste gedrückt ist |
| if (e.Button != MouseButtons.Left) |
| return; |
| lastPoint = new Point(e.X, e.Y); |
| mouseFlag = true; |
| } |
| private void Form1_MouseUp(object sender, MouseEventArgs e) { |
| mouseFlag = false; |
| } |
| } |

Hier klicken, um das Bild zu vergrößern
Abbildung 22.32 Eine sehr ansehnliche Handzeichnung mit der Maus
Weil das Programm nur auf die linke Maustaste reagieren soll, wird der Zustand der gedrückten linken Maustaste durch die boolesche Variable mouseFlag beschrieben. Ist die Variable true, ist die linke Taste gedrückt. Gesetzt wird mouseFlag im MouseDown-Ereignis ebenso wie die Variable lastPoint, die im MouseDown-Ereignis den Startpunkt der Maus auf der Form festhält. lastPoint wird als Startpunkt der Zeichnung interpretiert, die im sich anschließenden MouseMove-Ereignis gezeichnet wird.
Solange die Maustaste gedrückt bleibt, werden unablässig MouseMove-Ereignisse ausgelöst. Weil bei jeder Auslösung eine neue Linie gezeichnet wird, dient lastPoint auch der kontinuierlichen Aneinanderreihung der Linien, die sich als geschlossenes grafisches Element präsentieren.
Das MouseMove-Ereignis stellt die Referenz auf den Grafikkontext des Formulars nicht per Definition bereit. Diesen besorgen wir uns daher mit der Anweisung:
| Graphics grfx = this.CreateGraphics(); |
Im MouseUp-Ereignis wird mouseFlag auf den neutralen false-Zustand zurückgesetzt.
Führen Sie das Programm aus, werden Sie allerdings noch einen nicht akzeptablen Nebeneffekt erkennen: Die Grafik wird nicht restauriert, wenn sich ein anderes Fenster über die Form schiebt und danach das Grafikfenster wieder freigibt. Zur Lösung des Problems würde es sich anbieten, alle Punkte der Grafik in einem Array zu speichern. Der Aufwand der Codeimplementierung steigt aber auch mit der Komplexität der Grafik deutlich an. Unter Umständen können sogar Performanceaspekte gegen diesen Lösungsansatz sprechen, wenn sich das Bild aus zu vielen Linien zusammensetzt.
Besser ist es, in solchen Fällen die Grafik in ein Bitmap zu kopieren. Das erfordert nur wenig Programmieraufwand, und die Performance ist unabhängig von der Komplexität der Grafik. Im folgenden Beispiel MiniDraw wird dieses Verfahren gezeigt. Darüber hinaus kann die Freihandzeichnung gespeichert und zu einem späteren Zeitpunkt auch wieder geladen werden, um die begonnene Zeichnung weiter zu bearbeiten.
| // ------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 22\MiniDraw |
| //-------------------------------------------------------------- |
| public partial class Form1 : Form { |
| private Bitmap bmp; |
| private Graphics graphBMP; |
| private Point lastPoint; |
| public Form1() { |
| InitializeComponent(); |
| bmp = new Bitmap(this.ClientSize.Width, this.ClientSize.Height); |
| graphBMP = Graphics.FromImage(bmp); |
| } |
| private void Form1_Paint(object sender, PaintEventArgs e) { |
| e.Graphics.DrawImage(bmp,0,0); |
| } |
| private void Form1_MouseMove(object sender, MouseEventArgs e) { |
| Point newPoint = new Point(e.X, e.Y); |
| // prüfen, ob die rechte Maustaste gedrückt ist |
| if (e.Button == MouseButtons.Right) { |
| newPoint = new Point(e.X, e.Y); |
| // Pixel löschen |
| graphBMP.DrawLine(new Pen(this.BackColor, 10), lastPoint, newPoint); |
| lastPoint = newPoint; |
| } |
| // wenn linke Maustaste gedrückt ist, Pixel zeichnen |
| else if(e.Button == MouseButtons.Left) { |
| graphBMP.DrawLine(new Pen(Brushes.Yellow,5), lastPoint, newPoint); |
| lastPoint = newPoint; |
| } |
| // der Aufruf von Invalidate führt zu einem Flackern der Anzeige |
| Graphics g = this.CreateGraphics(); |
| g.DrawImage(bmp,0,0); |
| g.Dispose(); |
| } |
| private void Form1_MouseDown(object sender, MouseEventArgs e) { |
| lastPoint = new Point(e.X, e.Y); |
| } |
| private void mnuSave_Click(object sender, EventArgs e) { |
| DialogResult dr = saveFileDialog1.ShowDialog(); |
| if(dr == DialogResult.OK) |
| // Bitmap speichern |
| bmp.Save(saveFileDialog1.FileName,ImageFormat.Bmp); |
| } |
| private void mnuOpen_Click(object sender, EventArgs e) { |
| // Verarbeitung des MouseMove-Ereignisses abschalten |
| this.MouseMove -= new MouseEventHandler(Form1_MouseMove); |
| DialogResult dr = openFileDialog1.ShowDialog(); |
| if(dr == DialogResult.OK) { |
| // Stream erzeugen |
| FileStream fs = new FileStream(openFileDialog1.FileName,FileMode.Open); |
| bmp = new Bitmap(fs); |
| // Stream schließen |
| fs.Close(); |
| graphBMP = Graphics.FromImage(bmp); |
| this.Invalidate(); |
| } |
| // Nachrichtenwarteschlange leeren |
| Application.DoEvents(); |
| // Verarbeitung des MouseMove-Ereignisses einschalten |
| this.MouseMove += new MouseEventHandler(Form1_MouseMove); |
| } |
| } |
Auf Klassenebene sind die drei Variablen bmp, graphBMP und lastPoint deklariert. bmp ist die Bitmap, in der die Zeichenoperationen ihr Ziel finden und es mit der Methode DrawImage dem Grafikkontext der Form übergeben wird. graphBMP speichert den Grafikkontext auf die Bitmap, und in lastPoint sind die Koordinaten enthalten, die der letzten registrierten Mauszeigerposition entsprechen.
Wird zur Laufzeit die Maus bewegt, während die linke Maustaste gedrückt ist, wird eine Linie entsprechend der x- und y-Koordinate des Mauszeigers gezeichnet. Analog wird mit der rechten Maustaste die Linie gelöscht, indem die Hintergrundfarbe der gezeichneten Linie der Hintergrundfarbe der Form gesetzt wird.
Das Zeichnen erfolgt im Ereignis MouseMove mit der Methode DrawLine. Gezeichnet wird dabei in den Grafikkontext der Bitmap:
| graphBMP.DrawLine(...); |
Damit die neue Linie auch in der Form sichtbar wird, muss man die Bitmap dem Grafikkontext der Form übergeben:
| Graphics g = this.CreateGraphics(); |
| g.DrawImage(bmp,0,0); |
An dieser Stelle wäre man vielleicht dazu geneigt, mit Invalidate das Paint-Ereignis der Form explizit auszulösen, das denselben Code enthält. Allerdings tritt dabei ein Flackern auf, das mit den beiden Anweisungen vermieden wird.
Das Öffnen und Speichern einer Bitmap erfolgt über ein Menü. In beiden Fällen wird dazu ein Standarddialog zur Auswahl bzw. Festlegung der BMP-Datei eingesetzt. Während das Speichern keine Probleme verursacht, ist dem Öffnen einer Bitmap-Datei noch einmal die ganze Aufmerksamkeit zu widmen. Das Problem ist, dass eine Bitmap zum Speichern die Methode Save anbietet, aber dass es dazu keine Entsprechung zum Speichern gibt. Stattdessen müssen wir einen Konstruktor der Klasse Bitmap aufrufen.
Wenn man sich die Liste der Konstruktoren ansieht, ist es im ersten Augenblick verlockend, auf die Überladung zuzugreifen, der eine Zeichenfolge mit der Pfadangabe übergeben wird.
| public Bitmap(string filename); |
Dieser Konstruktor birgt aber eine Gefahr in sich, die nicht sofort zu erkennen ist. Er erzeugt zwar eine Bitmap anhand der übergebenen Zeichenfolge, aber die Datei, die dazu geöffnet werden muss, wird nicht geschlossen. Die Folge ist, dass die Bitmap anschließend zwar bearbeitet werden kann, aber eine Speicherung unter demselben Dateinamen im gleichen Pfad nicht möglich ist und einen Laufzeitfehler verursacht.
Die Lösung führt über den Konstruktor, dem ein Stream-Objekt übergeben wird. Der Stream, der explizit erzeugt wird, leitet die Daten der eingelesenen Datei an den Bitmap-Konstruktor weiter. Danach kann der Stream ordentlich geschlossen werden.
| FileStream fs = new FileStream(...); |
| bmp = new Bitmap(fs); |
| fs.Close(); |
Das Öffnen einer Datei kann durch einen Doppelklick im Standarddialog erfolgen. Es zeigt sich, dass dann durch den Aufruf des MouseMove-Ereignishandlers eine Linie gezeichnet wird, die nicht zu der in der Datei gespeicherten Darstellung gehört. Aus diesem Grund wird im Click-Ereignis des Menüs »Öffnen« zuerst der MouseMove-Handler deregistriert und nach Beendigung aller Operationen neu registriert. Vor der Neuregistrierung muss darüber hinaus mit Application.DoEvents die Nachrichtenschleife geleert werden.
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.